package com.aspose.words.examples.mail_merge;

import com.aspose.words.IMailMergeDataSource;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;
import java.util.HashMap;

/**
 * A custom mail merge data source that allows you to merge data from an XML
 * document into Word templates. This class demonstrates how data can be read
 * from a custom data source (XML parsed and loaded into a DOM) and merged into
 * a document using the IMailMergeDataSource interface.
 *
 * An instance of this class represents a single table in the data source and in
 * the template. Note: We are using the Document and Node class from the
 * org.w3c.dom package here and not from Aspose.Words.
 */
public class XmlMailMergeDataTable implements IMailMergeDataSource {
	/**
	 * Creates a new XmlMailMergeDataSource for the specified XML document and
	 * table name.
	 *
	 * @param xmlDoc
	 *            The DOM object which contains the parsed XML data.
	 * @param tableName
	 *            The name of the element in the data source where the data of
	 *            the region is extracted from.
	 */
	public XmlMailMergeDataTable(org.w3c.dom.Document xmlDoc, String tableName) throws Exception {
		this(xmlDoc.getDocumentElement(), tableName);
	}

	/**
	 * Private constructor that is also called by GetChildDataSource.
	 */
	private XmlMailMergeDataTable(Node rootNode, String tableName) throws Exception {
		mTableName = tableName;

		// Get the first element on this level matching the table name.
		mCurrentNode = (Node) retrieveExpression("./" + tableName).evaluate(rootNode, XPathConstants.NODE);
	}

	/**
	 * The name of the data source. Used by Aspose.Words only when executing
	 * mail merge with repeatable regions.
	 */
	public String getTableName() {
		return mTableName;
	}

	/**
	 * Aspose.Words calls this method to get a value for every data field.
	 */
	public boolean getValue(String fieldName, Object[] fieldValue) throws Exception {
		// Attempt to retrieve the child node matching the field name by using XPath.
		Node value = (Node) retrieveExpression(fieldName).evaluate(mCurrentNode, XPathConstants.NODE);
		// We also look for the field name in attributes of the element node.
		Element nodeAsElement = (Element) mCurrentNode;

		if (value != null) {
			// Field exists in the data source as a child node, pass the value and return true.
			// This merges the data into the document.
			fieldValue[0] = value.getTextContent();
			return true;
		} else if (nodeAsElement.hasAttribute(fieldName)) {
			// Field exists in the data source as an attribute of the current node, pass the value and return true.
			// This merges the data into the document.
			fieldValue[0] = nodeAsElement.getAttribute(fieldName);
			return true;
		} else {
			// Field does not exist in the data source, return false.
			// No value will be merged for this field and it is left over in the document.
			return false;
		}
	}

	/**
	 * Moves to the next record in a collection. This method is a little
	 * different then the regular implementation as we are walking over an XML
	 * document stored in a DOM.
	 */
	public boolean moveNext() {
		if (!isEof()) {
			// Don't move to the next node if this the first record to be merged.
			if (!mIsFirstRecord) {
				// Find the next node which is an element and matches the table name represented by this class.
				// This skips any text nodes and any elements which belong to a different table.
				do {
					mCurrentNode = mCurrentNode.getNextSibling();
				} while ((mCurrentNode != null) && !(mCurrentNode.getNodeName().equals(mTableName) && (mCurrentNode.getNodeType() == Node.ELEMENT_NODE)));
			} else {
				mIsFirstRecord = false;
			}
		}

		return (!isEof());
	}

	/**
	 * If the data source contains nested data this method will be called to
	 * retrieve the data for the child table. In the XML data source nested data
	 * this should look like this:
	 *
	 * <Tables> 
	 * 	 <ParentTable> 
	 *     <Name>ParentName</Name>
	 *     <ChildTable> 
	 *       <Text>Content</Text> 
	 *     </ChildTable> 
	 *   </ParentTable>
	 * </Tables>
	 */
	public IMailMergeDataSource getChildDataSource(String tableName) throws Exception {
		return new XmlMailMergeDataTable(mCurrentNode, tableName);
	}

	private boolean isEof() {
		return (mCurrentNode == null);
	}

	/**
	 * Returns a cached version of a compiled XPathExpression if available,
	 * otherwise creates a new expression.
	 */
	private XPathExpression retrieveExpression(String path) throws Exception {
		XPathExpression expression;

		if (mExpressionSet.containsKey(path)) {
			expression = (XPathExpression) mExpressionSet.get(path);
		} else {
			expression = mXPath.compile(path);
			mExpressionSet.put(path, expression);
		}
		return expression;
	}

	/**
	 * Instance variables.
	 */
	private Node mCurrentNode;
	private boolean mIsFirstRecord = true;
	private final String mTableName;
	private final HashMap mExpressionSet = new HashMap();
	private final XPath mXPath = XPathFactory.newInstance().newXPath();
}
